// Copyright (c) Microsoft Corporation.  All rights reserved.

using System;
using System.Collections.Generic;
using System.Text;

using Microsoft.Win32;
using System.Security;
using System.Security.Principal;
using System.Security.AccessControl;

namespace SSISAccess
{
    [Flags]
    internal enum LaunchPermission
    {
        LocalLaunch = 3,
        RemoteLaunch = 5,
        LocalActivation = 9,
        RemoteActivation = 17,
        All = LocalLaunch | RemoteLaunch | LocalActivation | RemoteActivation
    }

    [Flags]
    internal enum AccessPermission
    {
        Local = 3,
        Remote = 5,
        All = Local | Remote
    }

    internal class MsDtsDCOMPermission
    {
        private const string MsDtsPropertiesKey = @"AppID\{F38B7F09-979B-4241-80D9-2EADED02954F}";
        private const string LaunchValueName = "LaunchPermission";
        private const string AccessValueName = "AccessPermission";

        private SecurityIdentifier userSid;

        private MsDtsDCOMPermission() { }
        public MsDtsDCOMPermission(SecurityIdentifier userSid) {
            this.userSid = userSid;
        }

        public void SetAccess()
        {
            RegistryKey dtsKey = null;
            try
            {
                dtsKey = Registry.ClassesRoot.OpenSubKey(MsDtsPropertiesKey, true);

                SetLaunchPermission(dtsKey);
                SetAccessPermission(dtsKey);
            }
            finally
            {
                if (dtsKey != null)
                {
                    dtsKey.Close();
                }
            }
        }

        public bool HasAccess() {
            bool retVal = false;
            RegistryKey dtsKey = null;

            try
            {
                dtsKey = Registry.ClassesRoot.OpenSubKey(MsDtsPropertiesKey, false);

                retVal = GetLaunchPermission(dtsKey);
                if (retVal)
                {
                    retVal = GetAccessPermission(dtsKey);
                }
            }
            finally
            {
                if (dtsKey != null)
                {
                    dtsKey.Close();
                }
            }

            return retVal;
        }

        private void SetAccessPermission(RegistryKey regKey)
        {
            RawSecurityDescriptor descriptor = GetDescriptor(regKey, AccessValueName);
            ModifyDACL(descriptor.DiscretionaryAcl, (int)AccessPermission.All);
            SetDescriptor(regKey, AccessValueName, descriptor);
        }

        private void SetLaunchPermission(RegistryKey regKey)
        {
            RawSecurityDescriptor descriptor = GetDescriptor(regKey, LaunchValueName);
            ModifyDACL(descriptor.DiscretionaryAcl, (int)LaunchPermission.All);
            SetDescriptor(regKey, LaunchValueName, descriptor);
        }

        private bool GetAccessPermission(RegistryKey regKey)
        {
            RawSecurityDescriptor descriptor = GetDescriptor(regKey, AccessValueName);
            return CheckDACL(descriptor.DiscretionaryAcl, (int)AccessPermission.All);
        }

        private bool GetLaunchPermission(RegistryKey regKey)
        {
            RawSecurityDescriptor descriptor = GetDescriptor(regKey, LaunchValueName);
            return CheckDACL(descriptor.DiscretionaryAcl, (int)LaunchPermission.All);
        }

        private static RawSecurityDescriptor GetDescriptor(RegistryKey regKey, string regValueName)
        {
            byte[] binaryDescriptor = (byte[])regKey.GetValue(regValueName);
            return new RawSecurityDescriptor(binaryDescriptor, 0);
        }

        private static void SetDescriptor(RegistryKey regKey, string regValueName, RawSecurityDescriptor descriptor)
        {
            byte[] binaryDescriptor = new byte[descriptor.BinaryLength];
            descriptor.GetBinaryForm(binaryDescriptor, 0);
            regKey.SetValue(regValueName, binaryDescriptor);
        }

        private void ModifyDACL(RawAcl dacl, int privileges)
        {
            int aceIndex = GetDACLIndexOfUser(dacl);

            if (-1 == aceIndex)
            {
                // add a new ace
                CommonAce ace = new CommonAce(AceFlags.None, AceQualifier.AccessAllowed, privileges, this.userSid, false, null);
                dacl.InsertAce(dacl.Count, ace);
            }
            else
            {
                // change the existing ace
                SecurityIdentifier sid = ((CommonAce)dacl[aceIndex]).SecurityIdentifier;
                dacl[aceIndex] = new CommonAce(AceFlags.None, AceQualifier.AccessAllowed, privileges, sid, false, null);
            }
        }

        private bool CheckDACL(RawAcl dacl, int privileges)
        {
            bool retVal = false;
            int aceIndex = GetDACLIndexOfUser(dacl);

            if (aceIndex >= 0)
            {
                // change the existing ace
                CommonAce ace = (CommonAce)dacl[aceIndex];
                retVal = (ace.AceQualifier == AceQualifier.AccessAllowed && ace.AccessMask == privileges);
            }

            return retVal;
        }

        private int GetDACLIndexOfUser(RawAcl dacl)
        {
            int retVal = -1;

            for (int i = 0; i < dacl.Count; i++)
            {
                SecurityIdentifier sid = ((CommonAce)dacl[i]).SecurityIdentifier;
                if (sid.Equals(this.userSid))
                {
                    retVal = i;
                    break;
                }
            }

            return retVal;
        }
    }
}
